#include "Audio.h"

vector<audioDevice> devices;
RtAudio adac;

/* * * * * * * * * * * * * * * FUNCTION DEFINITIONS * * * * * * * * * * * * * */

#define DEBUGGING true

/**
* Primary function responsible for generating an output buffer from an input
* buffer. Interfaces with the RTAudio class and provides a framework to process
* input/output signals. By default, just copies the input buffer directly to
* the output buffer.
*
* @param outputBuffer The buffer of data to be written to the output of the 
* program (currently the speakers of the computer)
*
* @param inputBuffer The buffer of data read from the input to the program
* (either the line-in port or the built-in microphone)
*
* @param nBufferFrames The number of frames (data samples) in inputBuffer and 
* outputBuffer
*
* @param streamTime The time since the stream was opened at the first frame of
* the buffer
*
* @param status A variable containing information about the status of the
* stream. If stream is false, then everything is normal
*
* @param data A variable containing a pointer to any other data needed for this
* function (including a pointer to any callback functions needed for audio 
* processing and any data required by these functions)
*
* @return An integer encoding information about the successful execution of 
* this function. A zero indicates successful execution with no problems.
*/
int fileRead(void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames, 
		  double streamTime, RtAudioStreamStatus status, void *data)
{
	// More accurate (or at least consistent) way of keeping track of the time
	// at the start of the buffer than streamTime
	static unsigned int bufferStartTime = 0;

	// Checks to ensure the buffers have been filled correctly
	if ( status ) 
	{
		cout << "Stream over/underflow detected." << endl;
		//return -1;
	}

	// Extract the effect to process the input data buffer 
	Effect *effect = ((ioData*)data)->effect;

	// If no effect is specified, write the input buffer straight to the output 
	// buffer
	if(&effect == NULL)
	{
		unsigned int *bytes = ((ioData*)data)->bytes;
		memcpy( outputBuffer, inputBuffer, *bytes);
		return 0;
	}
	else
	{	unsigned int *bytes = ((ioData*)data)->bytes;
	inputBuffer = malloc(nBufferFrames * sizeof(double));
	void *tempBuffer = malloc(nBufferFrames * sizeof( MY_TYPE ));
	unsigned int count = fread( tempBuffer, sizeof( MY_TYPE ), nBufferFrames, ((ioData*)data)->fd);

	if(count < nBufferFrames)
		return 1;
	for(unsigned int i = 0; i < nBufferFrames; i++)
	{
		((double*)inputBuffer)[i] = (double)(((signed short *)tempBuffer)[i]);
	}
	free(tempBuffer);
	*bytes = (nBufferFrames - count) * sizeof( MY_TYPE );


	/*if ( count < nBufferFrames ) {

	unsigned int startByte = count * sizeof( MY_TYPE );
	memset( (char *)(inputBuffer)+startByte, 0, *bytes );
	}

	*/
	//*bytes = ((ioData*)data)->bytes;
	//memcpy( outputBuffer, inputBuffer, *bytes));
	//memcpy( outputBuffer, inputBuffer, *bytes);
	}

	// Create struct of data to be passed to processing function 
	processData pData;
	pData.bufferStartTime = bufferStartTime;
	pData.bytes = ((ioData*)data)->bytes;
	pData.nBufferFrames = nBufferFrames;

	// Increment bufferStartTime to be correct for next call to ioOps
	bufferStartTime += nBufferFrames / ((ioData*)data)->fs;
	//free(inputBuffer);
	// Process input data to obtain output data
	return effect->processSound(outputBuffer, inputBuffer, &pData);
	//return 0;
}

/**
 * Primary function responsible for generating an output buffer from an input
 * buffer. Interfaces with the RTAudio class and provides a framework to process
 * input/output signals. By default, just copies the input buffer directly to
 * the output buffer.
 *
 * @param outputBuffer The buffer of data to be written to the output of the 
 * program (currently the speakers of the computer)
 *
 * @param inputBuffer The buffer of data read from the input to the program
 * (either the line-in port or the built-in microphone)
 *
 * @param nBufferFrames The number of frames (data samples) in inputBuffer and 
 * outputBuffer
 *
 * @param streamTime The time since the stream was opened at the first frame of
 * the buffer
 *
 * @param status A variable containing information about the status of the
 * stream. If stream is false, then everything is normal
 *
 * @param data A variable containing a pointer to any other data needed for this
 * function (including a pointer to any callback functions needed for audio 
 * processing and any data required by these functions)
 *
 * @return An integer encoding information about the successful execution of 
 * this function. A zero indicates successful execution with no problems.
 */
int ioOps(void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames, 
					double streamTime, RtAudioStreamStatus status, void *data)
{
	// More accurate (or at least consistent) way of keeping track of the time
	// at the start of the buffer than streamTime
	static unsigned int bufferStartTime = 0;
	
	// Checks to ensure the buffers have been filled correctly
	if ( status ) 
		cout << "Stream over/underflow detected." << endl;
	
	// Extract the effect to process the input data buffer 
	Effect *effect = ((ioData*)data)->effect;
	
	// If no effect is specified, write the input buffer straight to the output 
	// buffer
	if(&effect == NULL)
	{
		unsigned int *bytes = ((ioData*)data)->bytes;
		memcpy( outputBuffer, inputBuffer, *bytes);
		return 0;
	}
	
	// Create struct of data to be passed to processing function 
	processData pData;
	pData.bufferStartTime = bufferStartTime;
	pData.bytes = ((ioData*)data)->bytes;
	pData.nBufferFrames = nBufferFrames;
	
	// Increment bufferStartTime to be correct for next call to ioOps
	bufferStartTime += nBufferFrames / ((ioData*)data)->fs;
	cout << "here" << endl;
	// Process input data to obtain output data
	return effect->processSound(outputBuffer, inputBuffer, &pData);
}


vector<audioDevice> *audioProbe(RtAudio & audio, int & defaultInput, int & defaultOutput)
{
	vector<audioDevice> *audioDevices = new vector<audioDevice>();

	// Create an api map.
	std::map<int, std::string> apiMap;
	apiMap[RtAudio::MACOSX_CORE] = "OS-X Core Audio";
	apiMap[RtAudio::WINDOWS_ASIO] = "Windows ASIO";
	apiMap[RtAudio::WINDOWS_DS] = "Windows Direct Sound";
	apiMap[RtAudio::UNIX_JACK] = "Jack Client";
	apiMap[RtAudio::LINUX_ALSA] = "Linux ALSA";
	apiMap[RtAudio::LINUX_OSS] = "Linux OSS";
	apiMap[RtAudio::RTAUDIO_DUMMY] = "RtAudio Dummy";

	// Get the compiled APIs
	vector< RtAudio::Api > apis;
	RtAudio::getCompiledApi( apis );
	RtAudio::DeviceInfo info;

	if( DEBUGGING )
	{
		cout << "Probing system audio devices..." << endl;
		cout << "\nCompiled APIs:\n";
		for ( unsigned int i=0; i<apis.size(); i++ )
			cout << "  " << apiMap[ apis[i] ] << endl;
		cout << "\nCurrent API: " << apiMap[ audio.getCurrentApi() ] << endl;
	}

	unsigned int devices = audio.getDeviceCount();

	if( DEBUGGING )
		cout << "\nFound " << devices << " device(s) ...\n";

	for (unsigned int i=0; i<devices; i++) {
		info = audio.getDeviceInfo(i);

		if(DEBUGGING)
			cout << "\nDevice Name = " << info.name << '\n';

		if ( info.probed == false ){
			if(DEBUGGING)
				cout << "Probe Status = UNsuccessful\n";
		}
		else {
			if(DEBUGGING)
			{
				cout << "Probe Status = Successful\n";
				cout << "Output Channels = " << info.outputChannels << '\n';
				cout << "Input Channels = " << info.inputChannels << '\n';
				cout << "Duplex Channels = " << info.duplexChannels << '\n';
			}

			audioDevice device;
			device.deviceNumber = i;
			device.inputChannels = info.inputChannels;
			device.outputChannels = info.outputChannels;
			device.deviceName = info.name;


			if ( info.isDefaultOutput )
			{
				if( DEBUGGING )
					cout << "This is the default output device.\n";
				defaultInput = i;
			}
			else if( DEBUGGING )
				cout << "This is NOT the default output device.\n";

			if ( info.isDefaultInput ) 
			{
				if( DEBUGGING )
					cout << "This is the default input device.\n";
				defaultOutput = i;
			}
			else if( DEBUGGING )
				cout << "This is NOT the default input device.\n";

			if ( info.nativeFormats == 0 )
				if( DEBUGGING )
					cout << "No natively supported data formats(?)!";
				else {
					if( DEBUGGING )
						cout << "Natively supported data formats:\n";
					if ( info.nativeFormats & RTAUDIO_SINT8 )
						if(DEBUGGING)
							cout << "  8-bit int\n";
					if ( info.nativeFormats & RTAUDIO_SINT16 )
					{
						device._16BitEnabled = true;
						if(DEBUGGING)
							cout << "  16-bit int\n";
					}
					else
						device._16BitEnabled = false;
					if ( info.nativeFormats & RTAUDIO_SINT24 )
						if(DEBUGGING)
							cout << "  24-bit int\n";
					if ( info.nativeFormats & RTAUDIO_SINT32 )
						if(DEBUGGING)
							cout << "  32-bit int\n";
					if ( info.nativeFormats & RTAUDIO_FLOAT32 )
						if(DEBUGGING)
							cout << "  32-bit float\n";
					if ( info.nativeFormats & RTAUDIO_FLOAT64 )
						if(DEBUGGING)
							cout << "  64-bit float\n";
				}
				if ( info.sampleRates.size() < 1 )
					cout << "No supported sample rates found!";
				else {
					if(DEBUGGING)
						cout << "Supported sample rates = ";
					device.sampleRates = info.sampleRates;
					if(DEBUGGING)
						for (unsigned int j=0; j<info.sampleRates.size(); j++)
							cout << info.sampleRates[j] << " ";
				}

				if(DEBUGGING)
					cout << endl;

				audioDevices->push_back(device);
		}
	}
	if(DEBUGGING)
		cout << endl;
	return audioDevices;
}

int printMainMenu()
{
	cout << "Opus Audio Processor" << endl;
	cout << "---------------------------------------------" << endl;
	cout << "1. Display Effect Parameters " << endl;
	cout << "2. Change Effect Parameters " << endl;
	cout << "3. Display Input/Output Devices " << endl;
	cout << "4. Change Input/Output Devices " << endl;
	cout << "5. Play .wav File " << endl;
	cout << "6. Start Realtime I/O Processing " << endl;
	cout << "7. Quit " << endl;

	int result;

	while(true){
		cout << endl << "Your Choice: ";

		cin >> result;
		if(result >= 1 && result <= 7)
			break;
		cout << "Invalid choice, please enter a number between 1 and 7" << endl;
	}

	cout << endl;

	return result;
}

void printDistortionParameters(int gain, int tone, int volume, int dirty)
{
	cout << "Current Distortion Parameters " << endl;
	cout << "----------------------------------" << endl;
	cout << "Tube Screamer Gain (0-100): " << gain << endl;
	cout << "Tube Screamer Tone (0-100): " << tone << endl;
	cout << "Tube Screamer Dirty Factor (0-100): " << dirty << endl;
	cout << "Tube Screamer Volume (0-100): " << volume << endl;
	cout << endl;
}

void setDistortionParameters(int & gain, int & tone, int & volume, int & dirty)
{
	cout << "Customize Tube Screamer Parameters" << endl;
	cout << "----------------------------------" << endl;

	while(true)
	{
		cout << "Enter value for Tube Screamer Gain (0-100): ";
		cin >> gain;
		if(gain >= 0 && gain <= 100)
			break;
		cout << endl;
		cout << "Please enter a valid number for Gain (0-100): ";
	}

	while(true)
	{
		cout << "Enter value for Tube Screamer Tone (0-100): ";
		cin >> tone;
		if(tone >= 0 && tone <= 100)
			break;
		cout << endl;
		cout << "Please enter a valid number for Tone (0-100): ";
	}

	while(true)
	{
		cout << "Enter value for Tube Screamer Dirty Factor (0-100): ";
		cin >> dirty;
		if(dirty >= 0 && dirty <= 100)
			break;
		cout << endl;
		cout << "Please enter a valid number for Dirty Factor (0-100): ";
	}

	while(true)
	{
		cout << "Enter value for Tube Screamer Volume (0-100): ";
		cin >> volume;
		if(dirty >= 0 && volume <= 100)
			break;
		cout << endl;
		cout << "Please enter a valid number for Volume (0-100): ";
	}
}

void listAudioDevices(vector<audioDevice> & devices, unsigned int iDevice, unsigned int oDevice)
{
	cout << "Currently Enabled Audio Input Devices" << endl;
	cout << "-------------------------------" << endl;
	for(unsigned short int i = 0; i < devices.size(); i ++)
	{
		if(devices[i].inputChannels > 0){
			cout << (int)(devices[i].deviceNumber) << ". " << devices[i].deviceName;
			if(i == iDevice)
				cout << " - Enabled";
			cout << endl;
		}
	}

	cout << endl;
	cout << "Currently Enabled Audio Output Devices" << endl;
	cout << "-------------------------------" << endl;
	for(unsigned short int i = 0; i < devices.size(); i ++)
	{
		if(devices[i].outputChannels > 0){
			cout << (int)(devices[i].deviceNumber) << ". " << devices[i].deviceName;
			if(i == oDevice)
				cout << " - Enabled ";
			cout << endl;
		}
	}
	cout << endl;
}

void playWaveFile(RtAudio & adac, unsigned int iDevice, unsigned int oDevice,
				 int gain, int tone, int volume, int dirty, vector<audioDevice> & devices)
{
	unsigned int channels = NUM_CHANNELS; 
	unsigned int bufferBytes;
	unsigned int iOffset = 0, oOffset = 0;
	unsigned int fs = SAMPLING_FREQUENCY;

	if ( devices.size() < 1 ) {
		cout << "\nNo audio devices found!\n";
		exit( 1 );
	}

	// Let RtAudio print messages to stderr.
	adac.showWarnings(true);

	// Set the same number of channels for both input and output.
	unsigned int bufferFrames = NUM_BUFFER_FRAMES;
	RtAudio::StreamParameters iParams, oParams;
	iParams.deviceId = iDevice;
	iParams.nChannels = channels;
	iParams.firstChannel = iOffset;
	oParams.deviceId = oDevice;
	oParams.nChannels = channels;
	oParams.firstChannel = oOffset;

	RtAudio::StreamOptions options;
	static TubeScreamer *tScream = NULL;
	try 
	{
	
		// Create TubeScreamer effect
		//if(tScream == NULL)
		tScream = new TubeScreamer(volume, gain, tone, dirty);

		// Create ioData struct to pass information into the ioOps callback
		ioData data;
		data.bytes = &bufferBytes;
		data.effect = (tScream);
		data.fs = fs;
		data.fd = fopen( "ben.wav", "rb" );

		// Open audio input/ouput streams
		//dac.openStream( &oParams, NULL, FORMAT, fs, &bufferFrames, &output, (void *)&data );
		adac.openStream( &oParams, NULL, FORMAT, fs, &bufferFrames, &fileRead, 
			(void *)&data, &options );
	}
	catch ( RtError& e ) {
		cout << '\n' << e.getMessage() << '\n' << endl;
		//exit( 1 );
	}


	bufferBytes = bufferFrames * channels * sizeof( double );

	// Test RtAudio functionality for reporting latency.
	if(DEBUGGING)
		cout << "\nStream latency = " << adac.getStreamLatency() << " frames" << endl;

	try {
		adac.startStream();

		if(DEBUGGING)
			cout << "\nPlaying raw file (buffer frames = " << bufferFrames << ")." << endl;
		while ( 1 ) {
			sleep( 100 ); // wake every 100 ms to check if we're done
			if ( adac.isStreamRunning() == false ) break;
		}

		/*char input;
		cout << "\nRunning ... press <enter> to quit (buffer frames = " << bufferFrames << ").\n";
		std::cin.get(input);

		// Stop the stream.
		adac.stopStream();
		cout << input;*/
	}
	catch ( RtError& e ) {
		cout << '\n' << e.getMessage() << '\n' << endl;
		goto cleanup;
	}

cleanup:
	if ( adac.isStreamOpen() ) adac.closeStream();
	if(tScream != NULL)
		delete tScream;
}


void playLiveStream(RtAudio & adac, unsigned int iDevice, unsigned int oDevice,
				 int gain, int tone, int volume, int dirty, vector<audioDevice> & devices)
{
	unsigned int channels = NUM_CHANNELS; 
	unsigned int bufferBytes;
	unsigned int iOffset = 0, oOffset = 0;
	unsigned int fs = SAMPLING_FREQUENCY;

	if ( devices.size() < 1 ) {
		cout << "\nNo audio devices found!\n";
		exit( 1 );
	}

	// Let RtAudio print messages to stderr.
	adac.showWarnings(true);

	// Set the same number of channels for both input and output.
	unsigned int bufferFrames = NUM_BUFFER_FRAMES;
	RtAudio::StreamParameters iParams, oParams;
	iParams.deviceId = iDevice;
	iParams.nChannels = channels;
	iParams.firstChannel = iOffset;
	oParams.deviceId = oDevice;
	oParams.nChannels = channels;
	oParams.firstChannel = oOffset;

	RtAudio::StreamOptions options;
	static TubeScreamer *tScream = NULL;
	try 
	{
	
		// Create TubeScreamer effect
		//if(tScream == NULL)
		tScream = new TubeScreamer(volume, gain, tone, dirty);

		// Create ioData struct to pass information into the ioOps callback
		ioData data;
		data.bytes = &bufferBytes;
		data.effect = (tScream);
		data.fs = fs;
		//data.fd = fopen( "ben.wav", "rb" );

		// Open audio input/ouput streams
		//adac.openStream( &oParams, NULL, FORMAT, fs, &bufferFrames, &output, (void *)&data );
		adac.openStream( &oParams, &iParams, FORMAT, fs, &bufferFrames, &ioOps, 
			(void *)&data, &options );
	}
	catch ( RtError& e ) {
		cout << '\n' << e.getMessage() << '\n' << endl;
		//exit( 1 );
	}


	bufferBytes = bufferFrames * channels * sizeof( double );

	// Test RtAudio functionality for reporting latency.
	if(DEBUGGING)
		cout << "\nStream latency = " << adac.getStreamLatency() << " frames" << endl;

	try {
		adac.startStream();
		
		char input;
		cout << "\nRunning ... type Q to quit (buffer frames = " << bufferFrames << ").\n";
		cin >> input;
		
		// Stop the stream.
		adac.stopStream();
	}
	catch ( RtError& e ) {
		cout << '\n' << e.getMessage() << '\n' << endl;
		goto cleanup;
	}
	
cleanup:
	if ( adac.isStreamOpen() ) adac.closeStream();
}

vector<audioDevice> *getDevices()
{
	return &devices;
}

void setDevices(vector<audioDevice> *newDevices)
{
	devices = *newDevices;
}

RtAudio *getADAC()
{
	return &adac;
}